use core::ffi::c_void;

use alloc::boxed::Box;

use crate::{
    c_src::{
        osRtxThread_t, osThreadAttr_t, osThreadId_t, osThreadJoin, osThreadJoinable, osThreadNew,
    },
    osPriority_t,
};

use super::{DynamicMem, Error};

const CB_SIZE: usize = size_of::<osRtxThread_t>();

unsafe impl Send for Thread {}
unsafe impl Sync for Thread {}

pub struct Thread {
    id: osThreadId_t,
    _cb_mem: DynamicMem,
    _stack_mem: DynamicMem,
}

impl Drop for Thread {
    fn drop(&mut self) {
        unsafe {
            osThreadJoin(self.id);
        }
    }
}

impl Thread {
    
    pub fn spawn2<T: Send + 'static, F: FnOnce() -> T + Send + 'static>(
        name: &'static str,
        stack_size: usize,
        priority: osPriority_t,
        f: F,
    ) -> Result<Self, Error> {
        let cb_mem = DynamicMem::new2(CB_SIZE)?;
        let stack_mem = DynamicMem::new2(stack_size)?;

        extern "C" fn task_fn<T: Send + 'static, F: FnOnce() -> T + Send + 'static>(
            args: *mut c_void,
        ) {
            let f = unsafe { Box::from_raw(args as *mut F) };
            f();
        }
        let args = Box::new(f);
        let fn_arg = Box::into_raw(args);
        let id = unsafe {
            let attr = osThreadAttr_t {
                name: name.as_ptr() as _,
                attr_bits: osThreadJoinable,
                cb_mem: cb_mem.ptr as _,
                cb_size: CB_SIZE as _,
                stack_mem: stack_mem.ptr as _,
                stack_size: stack_size as _,
                priority,
                tz_module: 0,
                reserved: 0,
            };
            osThreadNew(Some(task_fn::<T, F>), fn_arg as _, &attr)
        };

        if id.is_null() {
            Err(Error::New)
        } else {
            Ok(Self {
                id,
                _cb_mem: cb_mem,
                _stack_mem: stack_mem,
            })
        }
    }

    pub fn spawn<T: Send + 'static, F: FnOnce() -> T + Send + 'static>(
        name: &'static str,
        stack_size: usize,
        priority: osPriority_t,
        f: F,
    ) -> Self {
        let cb_mem = DynamicMem::new(CB_SIZE);
        let stack_mem = DynamicMem::new(stack_size);

        extern "C" fn task_fn<T: Send + 'static, F: FnOnce() -> T + Send + 'static>(
            args: *mut c_void,
        ) {
            let f = unsafe { Box::from_raw(args as *mut F) };
            f();
        }
        let args = Box::new(f);
        let fn_arg = Box::into_raw(args);
        let id = unsafe {
            let attr = osThreadAttr_t {
                name: name.as_ptr() as _,
                attr_bits: osThreadJoinable,
                cb_mem: cb_mem.ptr as _,
                cb_size: CB_SIZE as _,
                stack_mem: stack_mem.ptr as _,
                stack_size: stack_size as _,
                priority,
                tz_module: 0,
                reserved: 0,
            };
            osThreadNew(Some(task_fn::<T, F>), fn_arg as _, &attr)
        };

        if id.is_null() {
            panic!("thread new fail");
        }

        Self {
            id,
            _cb_mem: cb_mem,
            _stack_mem: stack_mem,
        }
    }
}
